//=============================================================================
// Copyright 2006-2010 Daniel W. Dyer
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//=============================================================================
package org.dmgenie.mtech.ke.ga;

import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.FlowLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.TreeMap;

import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSpinner;
import javax.swing.SpinnerNumberModel;
import javax.swing.SpringLayout;
import javax.swing.SwingUtilities;
import org.uncommons.maths.random.MersenneTwisterRNG;
import org.uncommons.maths.random.PoissonGenerator;
import org.uncommons.maths.random.Probability;
import org.uncommons.swing.SpringUtilities;
import org.uncommons.swing.SwingBackgroundTask;
import org.uncommons.watchmaker.examples.AbstractExampleApplet;
import org.uncommons.watchmaker.examples.EvolutionLogger;
import org.uncommons.watchmaker.examples.monalisa.ColouredPolygon;
import org.uncommons.watchmaker.framework.CandidateFactory;
import org.uncommons.watchmaker.framework.EvaluatedCandidate;
import org.uncommons.watchmaker.framework.EvolutionEngine;
import org.uncommons.watchmaker.framework.EvolutionObserver;
import org.uncommons.watchmaker.framework.EvolutionaryOperator;
import org.uncommons.watchmaker.framework.GenerationalEvolutionEngine;
import org.uncommons.watchmaker.framework.PopulationData;
import org.uncommons.watchmaker.framework.SelectionStrategy;
import org.uncommons.watchmaker.framework.TerminationCondition;
import org.uncommons.watchmaker.framework.factories.ListPermutationFactory;
import org.uncommons.watchmaker.framework.interactive.InteractiveSelection;
import org.uncommons.watchmaker.framework.interactive.Renderer;
import org.uncommons.watchmaker.framework.operators.EvolutionPipeline;
import org.uncommons.watchmaker.framework.operators.ListOrderCrossover;
import org.uncommons.watchmaker.framework.operators.ListOrderMutation;
import org.uncommons.watchmaker.framework.selection.RankSelection;
import org.uncommons.watchmaker.framework.termination.GenerationCount;
import org.uncommons.watchmaker.framework.termination.TargetFitness;
import org.uncommons.watchmaker.swing.AbortControl;
import org.uncommons.watchmaker.swing.SelectionStrategyControl;
import org.uncommons.watchmaker.swing.SwingConsole;
import org.uncommons.watchmaker.swing.evolutionmonitor.EvolutionMonitor;

import org.dmgenie.mtech.ke.ga.util.*;
import org.dmgenie.mtech.ke.ga.model.*;
import org.dmgenie.mtech.ke.ga.view.*;

/**
 * Watchmaker Framework implementation of Dawkin's biomorph program. 
 * @author Daniel Dyer
 */
public class LoadDistributionApplet extends AbstractExampleApplet
{
    private SwingConsole console;
    private JDialog selectionDialog;
    private LoadsView loadsView = new LoadsView();
    private EvolutionMonitor<List<Integer>> monitor;
    private AbortControl abortControl;
    private List<EvaluatedCandidate<List<Integer>>> candidatesOfGeneration = null;
    /**
     * Initialise and layout the GUI.
     * @param container The Swing component that will contain the GUI controls.
     */
    @Override
    protected void prepareGUI(Container container)
    {
        console = new SwingConsole(5);
        ////selectionDialog = new JDialog((JFrame) null, "Results", true);
        //loadsView = new LoadsView();
        
        container.add(new ControlPanel(), BorderLayout.WEST);
        //container.add(loadsView, BorderLayout.CENTER);

        monitor = new EvolutionMonitor<List<Integer>>();
        container.add(monitor.getGUIComponent(), BorderLayout.CENTER);//SOUTH not enough space
        
        ////selectionDialog.add(loadsView, BorderLayout.CENTER);//console
        ////selectionDialog.setSize(800, 300);
        ////selectionDialog.validate();
    }


    /**
     * Helper method to create a background task for running the interactive evolutionary
     * algorithm.
     * @param populationSize How big the population used by the created evolution engine
     * should be.
     * @param generationCount How many generations to use when the evolution engine is
     * invoked.
     * @param random If true use random mutation, otherwise use Dawkins mutation.
     * @return A Swing task that will execute on a background thread and update
     * the GUI when it is done.
     */
    private SwingBackgroundTask<List<Integer>> createTask(final int populationSize, 
    					final SelectionStrategy<? super List<Integer>> selectionStrategy,
    					final int eliteCount,
    					final int terminationType, //0 for no. of generations, 1 for target fitness value
                        final int terminationCondition,
                        final double crossover,
                        final boolean mutationCheck,
                        final double mutation,
                        final int fitnessFunctionType) //0 for log, 1 for abs, 2 for squared
    {
        return new SwingBackgroundTask<List<Integer>>()
        {
            @Override
            protected List<Integer> performTask()
            {
            	Random rng = new MersenneTwisterRNG();
            	// Set-up evolution pipeline (cross-over followed by mutation).
                if (crossover == 0 && mutation == 0)
                {
                    throw new IllegalArgumentException("At least one of cross-over or mutation must be selected.");
                }
                
                List<EvolutionaryOperator<List<Integer>>> operators = new ArrayList<EvolutionaryOperator<List<Integer>>>(2);
                if (crossover != 0)
                {
                    operators.add(new ListOrderCrossover<Integer>(new Probability(crossover)));
                }
                if (mutationCheck)
                {
                    operators.add(new ListOrderMutation<Integer>(new PoissonGenerator(mutation, rng),
                                                                new PoissonGenerator(1.5, rng)));
                }
                EvolutionaryOperator<List<Integer>> pipeline = new EvolutionPipeline<List<Integer>>(operators);
                
                List<Integer> packageIds = new ArrayList<Integer>(); 
                for (int i=0; i<64; i++) {//HARDCODED
                	packageIds.add(i);//putting the package id as 0 to 63
                }
                CandidateFactory<List<Integer>> candidateFactory = new ListPermutationFactory<Integer>(packageIds);
                
                //SelectionStrategy selectionStrategy = new RankSelection();
                
                GenerationalEvolutionEngine<List<Integer>> engine = 
                		new GenerationalEvolutionEngine<List<Integer>>
                			(candidateFactory,
        	                   pipeline,
        	                   (fitnessFunctionType == 0 ? new FitnessLogEvaluator() : 
        	                	   (fitnessFunctionType == 1 ? new FitnessAbsEvaluator() : new FitnessPower2Evaluator())),
        	                   selectionStrategy,
        	                   rng);
                
//                engine.setSingleThreaded(singleThreaded)SingleThreaded(true); // Performs better for very trivial fitness evaluations.
                //engine.addEvolutionObserver(new GenerationTracker());
                engine.addEvolutionObserver(monitor);
                
                TerminationCondition terminate;
                if (terminationType == 0)
                	terminate = new GenerationCount(terminationCondition);
                else 
                	terminate = new TargetFitness(terminationCondition, false);
                
//                List<Integer> results = engine.evolve(populationSize, 
//                        eliteCount, 
//                        terminate,
//                        abortControl.getTerminationCondition()); 
                if (candidatesOfGeneration == null) {
	                candidatesOfGeneration
	                	= engine.evolvePopulation(populationSize, 
	                        eliteCount, 
	                        terminate,
	                        abortControl.getTerminationCondition());
                } else {
                	List<List<Integer>> seedCandidates = new ArrayList<List<Integer>>();
                	for(int i=0; i<candidatesOfGeneration.size(); i++) {
                		seedCandidates.add(candidatesOfGeneration.get(i).getCandidate());
                	}
                	
                	candidatesOfGeneration
	                	= engine.evolvePopulation(populationSize, 
	                        eliteCount, 
	                        seedCandidates,
	                        terminate,
	                        abortControl.getTerminationCondition());
                }
                
                List<Integer> results;
                int indexOfBest = 0;
                double fitness = candidatesOfGeneration.get(0).getFitness(); //must find smallest value
                for(int i=0; i<candidatesOfGeneration.size(); i++) {
            		if (fitness > candidatesOfGeneration.get(i).getFitness()) {
            			fitness = candidatesOfGeneration.get(i).getFitness();
            			indexOfBest = i;
            		}
            	}
                results = candidatesOfGeneration.get(indexOfBest).getCandidate();
                
                System.out.println("Original result: " + results.toString());
                
                //sorting to get the smallest weight at the bottom
                for (int i = 0; i < results.size(); i=i+4)   //n=level 0 to 3
                {
                    //each stack
                	TreeMap<Double, Integer> map = new TreeMap<Double, Integer>();	//weight, id cos only key can be sorted
                    for (int j=0; j<4; j++) //4 levels
                    {
                    	map.put(LoadsWeight.getWeight(results.get(i+j)), results.get(i+j)); //permutation starts from 0 but textbook packageId starts from 1
                    }
                    int j=0;
                    for (Map.Entry<Double, Integer> load : map.entrySet()) {
                        //System.out.println("key: " + load.getKey() + ", value: " + load.getValue()); 
                    	results.set(i+j, load.getValue()); //permutation starts from 0 but packageId starts from 1
                    	j++;
                    }
                }
                System.out.println("Sorted each stack : " + results.toString());
                
                loadsView.setSolution(results);
                loadsView.revalidate();
                
                return results;     
            }
        };
    }


    /**
     * Entry point for running this example as an application rather than an applet.
     * @param args Program arguments (ignored).
     */
    public static void main(String[] args)
    {
        new LoadDistributionApplet().displayInFrame("Watchmaker Framework - Load Distribution");
    }


    /**
     * Simple observer to update the dialog title every time the evolution advances
     * to a new generation.
     */
    private final class GenerationTracker implements EvolutionObserver<List<Integer>>
    {
        public void populationUpdate(final PopulationData<? extends List<Integer>> populationData)
        {
            SwingUtilities.invokeLater(new Runnable()
            {
                public void run()
                {
                    selectionDialog.setTitle("List<Integer> Selection - Generation "
                                             + (populationData.getGenerationNumber() + 1)
                                             +" with Best Fitness: " + populationData.getBestCandidateFitness());
                    loadsView.setSolution(populationData.getBestCandidate());
                }
            });
        }
    }


    /**
     * Panel for controlling the evolutionary algorithm parameters.
     */
    private final class ControlPanel extends JPanel
    {
        private JSpinner populationSpinner;
        private JSpinner crossoverSpinner;
        private JCheckBox mutationCheckbox;
        private JSpinner mutationSpinner;
        private JSpinner elitismSpinner;
        private JComboBox fitnessFunctionCombo;
        private JComboBox terminateTypeCombo;
        private JLabel terminateConditionLabel;
        private JSpinner terminateConditionSpinner;
        private JButton showResultsButton;
        private SelectionStrategyControl<List<Integer>> selectionControl;
        
        ControlPanel()
        {
            super(new BorderLayout());
            add(createInputPanel(), BorderLayout.NORTH);
            add(createButtonPanel(), BorderLayout.SOUTH);
            setBorder(BorderFactory.createTitledBorder("Evolution Controls"));
        }


        private JComponent createInputPanel()
        {
            JPanel inputPanel = new JPanel(new SpringLayout());
            
            JLabel populationLabel = new JLabel("Population Size: ");
            populationSpinner = new JSpinner(new SpinnerNumberModel(500, 100, 1000, 50));
            populationLabel.setLabelFor(populationSpinner);
            inputPanel.add(populationLabel);
            inputPanel.add(populationSpinner);
            
            JLabel crossoverLabel = new JLabel("Cross-over: ");
            crossoverSpinner = new JSpinner(new SpinnerNumberModel(0.6, 0, 1, 0.05));
            crossoverLabel.setLabelFor(crossoverSpinner);
            inputPanel.add(crossoverLabel);
            inputPanel.add(crossoverSpinner);
            
            mutationCheckbox = new JCheckBox("Mutation", true);
            inputPanel.add(mutationCheckbox);
            inputPanel.add(new JLabel(""));
            
            JLabel mutationLabel = new JLabel("Mutation Count Poisson Mean: ");
            mutationSpinner = new JSpinner(new SpinnerNumberModel(1.5, 0, 10, 0.1));
            mutationLabel.setLabelFor(mutationSpinner);
            inputPanel.add(mutationLabel);
            inputPanel.add(mutationSpinner);
            
            JLabel fitnessFunctionLabel = new JLabel("Fitness Function: ");
            fitnessFunctionCombo = new JComboBox(new String[]{"Log of Summation", "Summation of Abs", "Summation of Squared"});
            fitnessFunctionCombo.setSelectedIndex(1);
            inputPanel.add(fitnessFunctionLabel);
            inputPanel.add(fitnessFunctionCombo);

            JLabel terminateTypeLabel = new JLabel("Termination Condition: ");
            terminateTypeCombo = new JComboBox(new String[]{"Number of Generations", "Target Fitness Value"});
            terminateConditionLabel = new JLabel("Number of Generations: ");
            terminateConditionSpinner = new JSpinner(new SpinnerNumberModel(100, 0, 10000, 1));
            terminateConditionLabel.setLabelFor(terminateConditionSpinner);
            terminateTypeCombo.addItemListener(new ItemListener()
            {
                public void itemStateChanged(ItemEvent itemEvent)
                {
                    if (terminateTypeCombo.getSelectedIndex() == 0)
                    {
                    	terminateConditionLabel.setText("Number of Generations: ");
                    	terminateConditionSpinner.setValue(100);
                    }
                    else
                    {
                    	terminateConditionLabel.setText("Target Fitness (lesser the better): ");
                    	terminateConditionSpinner.setValue(3);
                    }
                }
            });
            terminateTypeCombo.setSelectedIndex(1);
            inputPanel.add(terminateTypeLabel);
            inputPanel.add(terminateTypeCombo);
            inputPanel.add(terminateConditionLabel);
            inputPanel.add(terminateConditionSpinner);

            JLabel elitismLabel = new JLabel("Elitism: ");
            elitismSpinner = new JSpinner(new SpinnerNumberModel(3, 0, 10000, 1));
            elitismLabel.setLabelFor(elitismSpinner);
            inputPanel.add(elitismLabel);
            inputPanel.add(elitismSpinner);

            
            JLabel selectionLabel = new JLabel("Selection Strategy: ");
            List<SelectionStrategy<? super List<Integer>>> strategies
                = SelectionStrategyControl.createDefaultOptions(new Probability(0.95d), 0.5d);
            this.selectionControl = new SelectionStrategyControl<List<Integer>>(strategies);
            inputPanel.add(selectionLabel);
            selectionControl.getControl().setSelectedIndex(1);//roulette //selectionControl.getControl().getItemCount() - 1);
            inputPanel.add(selectionControl.getControl());



            SpringUtilities.makeCompactGrid(inputPanel, 9, 2, 30, 7, 7, 7);

            return inputPanel;
        }


        private JComponent createButtonPanel()
        {
            JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
            JButton startButton = new JButton("Start");
            startButton.addActionListener(new ActionListener()
            {
                public void actionPerformed(ActionEvent actionEvent)
                {
                	abortControl.reset();
                    createTask((Integer) populationSpinner.getValue(),
                    		selectionControl.getSelectionStrategy(),
                    		(Integer) elitismSpinner.getValue(),
                    		terminateTypeCombo.getSelectedIndex(),
                    		(Integer) terminateConditionSpinner.getValue(),
                    		(Double) crossoverSpinner.getValue(),
                    		mutationCheckbox.isSelected(),
                    		(Double) mutationSpinner.getValue(),
                    		fitnessFunctionCombo.getSelectedIndex()
                    		).execute();
                    showResultsButton.setEnabled(true);
                }
            });
            buttonPanel.add(startButton);
            
            abortControl = new AbortControl();
            buttonPanel.add(abortControl.getControl());
            abortControl.getControl().setEnabled(false);

            showResultsButton = new JButton("Results");
            showResultsButton.setEnabled(false);
            showResultsButton.addActionListener(new ActionListener()
            {
                public void actionPerformed(ActionEvent actionEvent)
                {
                	selectionDialog = new JDialog((JFrame) null, "Results", true);
                	selectionDialog.add(loadsView, BorderLayout.CENTER);//console
                    selectionDialog.setSize(800, 300);
                    selectionDialog.setVisible(true);
                }
            });
            buttonPanel.add(showResultsButton);
            
            JButton getNewPopulationsButton = new JButton("Get New Populations");
            getNewPopulationsButton.addActionListener(new ActionListener()
            {
                public void actionPerformed(ActionEvent actionEvent)
                {
                	abortControl.reset();
                    candidatesOfGeneration = null;
                }
            });
            buttonPanel.add(getNewPopulationsButton);
            return buttonPanel;
        }
    }
}
